using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;

using FlickrNet;

using Yorrick.FlickrMetadataSynchr.Data;
using Yorrick.FlickrMetadataSynchr.Flickr;
using Yorrick.FlickrMetadataSynchr.Events;
using Yorrick.FlickrMetadataSynchr.Local;
using Yorrick.FlickrMetadataSynchr.Match;
using System.Globalization;
using Yorrick.FlickrMetadataSynchr.Properties;

namespace Yorrick.FlickrMetadataSynchr.Sync
{
    /// <summary>
    /// Class that does the synchronization work. Implemented to run using a <see cref="BackgroundWorker"/>.
    /// </summary>
    /// <remarks>This class supports data binding by implementing <see cref="INotifyPropertyChanged"/>.</remarks>
    internal class SynchronizationWorker : INotifyPropertyChanged
    {
        #region Private fields
        /// <summary>
        /// The local folder to sync.
        /// </summary>
        private string _localFolder;

        /// <summary>
        /// Whether or not to include sub folders.
        /// </summary>
        private bool _includeSubFolders;

        /// <summary>
        /// Whether or not to download pictures that exist on Flickr but are not present
        /// on disk.
        /// </summary>
        private bool _downloadMissingPictures;

        /// <summary>
        /// The background worker controlling the background process.
        /// </summary>
        private BackgroundWorker _worker;

        /// <summary>
        /// The photoset on Flickr to sync.
        /// </summary>
        private Photoset _flickrPhotoset;

        /// <summary>
        /// Whether or not this instance has been initialized.
        /// </summary>
        private bool _isInitialized;

        /// <summary>
        /// Whether or not this instance is busy synching.
        /// </summary>
        private bool _busySynching;

        /// <summary>
        /// The object to lock on when checking if sync is busy.
        /// </summary>
        private object _syncLock = new Object();

        /// <summary>
        /// The Flickr helper. 
        /// </summary>
        private FlickrHelper _flickrHelper;

        /// <summary>
        /// The helper for locally stored pictures.
        /// </summary>
        private LocalPicturesHelper _localPicturesHelper;

        /// <summary>
        /// Whether or not to simulate updating metadata.
        /// </summary>
        private bool _simulateUpdatingMetadata = false;

        /// <summary>
        /// The desired behavior for synching tags.
        /// </summary>
        private TagsSyncBehavior _tagsSyncBehavior;

        /// <summary>
        /// The desired behavior for matching pictures.
        /// </summary>
        private MatchBehavior _matchBehavior;
        #endregion

        #region Constructor
        /// <summary>
        /// Constructs an instance.
        /// </summary>
        public SynchronizationWorker()
        {
        }
        #endregion

        #region Public properties
        /// <summary>
        /// The local folder to sync.
        /// </summary>
        public string LocalFolder
        {
            get
            {
                return _localFolder;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }

                _localFolder = value;

                // Store the selected folder in the user settings.
                Properties.Settings.Default.LastUsedPictureDirectory = _localFolder;
                Properties.Settings.Default.Save();

                NotifyPropertyChanged("LocalFolder");
                NotifyPropertyChanged("CanStartSync");
                NotifyPropertyChanged("CanDownloadPictures");
            }
        }

        /// <summary>
        /// Gets or sets if an alternate metadata mapping should be used for title and description to be more
        /// compatible with Adobe tools like Bridge.
        /// </summary>
        public bool UseAlternateLocalMetadataMapping
        {
            get
            {
                return Settings.Default.UseAlternateLocalMetadataMapping;
            }
            set
            {
                Settings.Default.UseAlternateLocalMetadataMapping = value;
                NotifyPropertyChanged("UseAlternateLocalMetadataMapping");
                Settings.Default.Save();
            }
        }

        /// <summary>
        /// Gets or sets whether or not to include sub folders.
        /// </summary>
        public bool IncludeSubFolders
        {
            get
            {
                return _includeSubFolders;
            }
            set
            {
                _includeSubFolders = value;

                // Store the setting in the user settings.
                Properties.Settings.Default.IncludeSubFolders = _includeSubFolders;
                Properties.Settings.Default.Save();

                NotifyPropertyChanged("IncludeSubFolders");
            }
        }

        /// <summary>
        /// Gets or sets whether or not to download pictures that exist on Flickr but are not present
        /// on disk.
        /// </summary>
        public bool DownloadMissingPictures
        {
            get
            {
                return _downloadMissingPictures;
            }
            set
            {
                _downloadMissingPictures = value;

                // Store the setting in the user settings.
                Properties.Settings.Default.DownloadMissingPictures = _downloadMissingPictures;
                Properties.Settings.Default.Save();

                NotifyPropertyChanged("DownloadMissingPictures");
            }
        }

        /// <summary>
        /// Whether or not the sync process can be started in the current state of this object.
        /// </summary>
        public bool CanStartSync
        {
            get
            {
                if (!String.IsNullOrEmpty(_localFolder) && _flickrPhotoset != null && !_busySynching && _flickrHelper.IsConnected)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Gets if the user can currently enable the option to download pictures.
        /// </summary>
        public bool CanDownloadPictures
        {
            get
            {
                if (BusySynching || _flickrHelper == null)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
        }

        /// <summary>
        /// Gets or sets whether or not this instance is busy synching.
        /// </summary>
        public bool BusySynching
        {
            get
            {
                return _busySynching;
            }
            private set
            {
                lock (_syncLock)
                {
                    if (_busySynching && value == true)
                    {
                        throw new InvalidOperationException(Properties.Messages.SyncAlreadyStarted);
                    }

                    _busySynching = value;
                }

                NotifyPropertyChanged("BusySynching");
                NotifyPropertyChanged("CanStartSync");
            }
        }

        /// <summary>
        /// Gets or sets the photoset on Flickr to sync.
        /// </summary>
        public Photoset FlickrPhotoset
        {
            get
            {
                return _flickrPhotoset;
            }
            set
            {
                _flickrPhotoset = value;

                NotifyPropertyChanged("FlickrPhotoset");
                NotifyPropertyChanged("CanStartSync");
            }
        }

        /// <summary>
        /// Gets or sets whether or not to simulate updating metadata.
        /// </summary>
        public bool SimulateUpdatingMetadata
        {
            get
            {
                return _simulateUpdatingMetadata;
            }
            set
            {
                _simulateUpdatingMetadata = value;

                // Store the setting in the user settings.
                Properties.Settings.Default.SimulateUpdatingPictures = _simulateUpdatingMetadata;
                Properties.Settings.Default.Save();

                NotifyPropertyChanged("SimulateUpdatingMetadata");
            }
        }

        /// <summary>
        /// Gets or set the desired behavior for synching tags.
        /// </summary>
        public TagsSyncBehavior TagsSyncBehavior
        {
            get
            {
                return _tagsSyncBehavior;
            }
            set
            {
                _tagsSyncBehavior = value;

                // Store the setting in the user settings.
                Properties.Settings.Default.TagsSyncBehavior = _tagsSyncBehavior;
                Properties.Settings.Default.Save();

                NotifyPropertyChanged("TagsSyncBehavior");
            }
        }

        /// <summary>
        /// Gets or set the desired behavior for synching tags.
        /// </summary>
        public MatchBehavior MatchBehavior
        {
            get
            {
                return _matchBehavior;
            }
            set
            {
                _matchBehavior = value;

                // Store the setting in the user settings.
                Properties.Settings.Default.MatchBehavior = _matchBehavior;
                Properties.Settings.Default.Save();

                NotifyPropertyChanged("MatchBehavior");
            }
        }
        #endregion

        #region Private methods
        /// <summary>
        /// Sets the initial local folder to use. This is the picture directory that was last
        /// used in the previous session or it is the user's Pictures folder if there is no
        /// last known folder.
        /// </summary>
        private void InitializeLocalFolder()
        {
            // Start in the last used picture directory or if there is none, the user's Pictures folder.
            string lastUsedPictureDirectory = Properties.Settings.Default.LastUsedPictureDirectory;
            if (lastUsedPictureDirectory.Length == 0)
            {
                lastUsedPictureDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
            }
            LocalFolder = lastUsedPictureDirectory;
            NotifyPropertyChanged("LocalFolder");
        }

        /// <summary>
        /// Makes sure that this instance has been initialized.
        /// Throws <see cref="InvalidOperationException"/> when not initialized.
        /// </summary>
        /// <exception cref="InvalidOperationException">When not initialized.</exception>
        private void EnsureIsInitialized()
        {
            if (!_isInitialized)
            {
                throw new InvalidOperationException(Properties.Messages.NotInitialized);
            }
        }

        /// <summary>
        /// Makes sure that the required properties have been set.
        /// Throws <see cref="InvalidOperationException"/> when not all required properties have been set.
        /// </summary>
        /// <exception cref="InvalidOperationException">When not all required properties have been set.</exception>
        private void EnsureRequiredPropertiesAreSet()
        {
            if (_flickrPhotoset == null || String.IsNullOrEmpty(_localFolder))
            {
                throw new InvalidOperationException(Properties.Messages.NotAllPropertiesSet);
            }
        }

        /// <summary>
        /// The core method for doing the synchronization.
        /// </summary>
        private void DoSync()
        {
            _flickrHelper.SimulateUpdatingMetadata = SimulateUpdatingMetadata;
            _localPicturesHelper.SimulateUpdatingMetadata = SimulateUpdatingMetadata;

            Trace.TraceInformation("Starting sync. Alternate metadata mapping: {0}.", Settings.Default.UseAlternateLocalMetadataMapping);

            int currentProgress = 0;
            int targetProgress;

            _worker.ReportProgress(currentProgress, Properties.Messages.SyncStarted);

            currentProgress += 5;
            
            /*********
             * 
             *  Fetching
             * 
             * ****************/


            IList<FlickrPictureMetadata> flickrPictureSyncMetadataList;
            IList<LocalPictureMetadata> localPictureSyncMetadataList;
            targetProgress = 50;
            bool cancel = FetchPictureMetadata(currentProgress, targetProgress, out flickrPictureSyncMetadataList, out localPictureSyncMetadataList);
            if (cancel)
            {
                return;
            }
            currentProgress = targetProgress;

            /*********
             * 
             *  Matching
             * 
             * ****************/

            // Deliberately slow things down because matching is so fast.
            targetProgress = 55;
            if (SimulateProgress(Properties.Messages.MatchingPictures, 500, currentProgress, targetProgress))
            {
                return;
            }
            currentProgress = targetProgress;

            IList<PicturePair> matchedPicturePairs;
            IList<FlickrPictureMetadata> unmatchedFlickrPictures;
            MatchPictures(currentProgress, flickrPictureSyncMetadataList, localPictureSyncMetadataList, out matchedPicturePairs, out unmatchedFlickrPictures);


            /*********
             * 
             *  Synching
             * 
             * ****************/

            // Deliberately slow things down because synching is so fast.
            targetProgress = 60;
            if (SimulateProgress(Properties.Messages.SynchingPictures, 500, currentProgress, targetProgress))
            {
                return;
            }
            currentProgress = targetProgress;

            SyncPictures(matchedPicturePairs);

            /*********
             * 
             *  Downloading (if downloading is enabled)
             * 
             * ****************/

            if (ShouldCancel())
            {
                return;
            }

            IList<LocalPictureMetadata> downloadedLocalPictures = null;

            if (_downloadMissingPictures)
            {
                targetProgress = 70;

                downloadedLocalPictures = DownloadUnmatchedPictures(unmatchedFlickrPictures, currentProgress, targetProgress);
                currentProgress = targetProgress;

                if (ShouldCancel())
                {
                    return;
                }
            }

            /*********
             * 
             *  Updating
             * 
             * ****************/

            List<FlickrPictureMetadata> updatedFlickrPictures = GetUpdatedFlickrPictures(matchedPicturePairs);
            List<LocalPictureMetadata> updatedLocalPictures = GetUpdatedLocalPictures(matchedPicturePairs);

            if (_downloadMissingPictures && downloadedLocalPictures != null)
            {
                updatedLocalPictures.AddRange(downloadedLocalPictures);
            }

            targetProgress = 80;
            UpdateFlickrPictureMetadata(updatedFlickrPictures, currentProgress, targetProgress);
            currentProgress = targetProgress;

            if (ShouldCancel())
            {
                return;
            }

            targetProgress = 100;
            UpdateLocalPictureMetadata(updatedLocalPictures, currentProgress, targetProgress);

            if (ShouldCancel())
            {
                return;
            }

            currentProgress = targetProgress;
            _worker.ReportProgress(currentProgress, Properties.Messages.SyncFinished);
        }

        /// <summary>
        /// Syncs the metadata for all matched picture pairs.
        /// </summary>
        /// <param name="matchedPicturePairs">The matched picture pairs.</param>
        private void SyncPictures(IList<PicturePair> matchedPicturePairs)
        {
            SyncStrategySettings strategySettings = new SyncStrategySettings(
               this.TagsSyncBehavior,
               Settings.Default.TagsCaseBehavior,
               Settings.Default.TagsSortBehavior,
               Settings.Default.AuthorSyncBehavior
               );
            MetadataSyncher metadataSyncher = new MetadataSyncher(matchedPicturePairs, strategySettings);
            metadataSyncher.SyncPictures();
        }

        /// <summary>
        /// Tries to match the pictures.
        /// </summary>
        /// <param name="currentProgress"></param>
        /// <param name="flickrPictureSyncMetadataList">The list of metadata for Flickr pictures.</param>
        /// <param name="localPictureSyncMetadataList">The listof metadata for local pictures.</param>
        /// <param name="matchedPicturePairs">Output parameter for all matched image pairs.</param>
        /// <param name="unmatchedFlickrPictures">Output parameter for all Flickr pictures that could not
        /// be matched.</param>
        private void MatchPictures(int currentProgress, IList<FlickrPictureMetadata> flickrPictureSyncMetadataList, IList<LocalPictureMetadata> localPictureSyncMetadataList, out IList<PicturePair> matchedPicturePairs, out IList<FlickrPictureMetadata> unmatchedFlickrPictures)
        {
            IMetadataMatcher matcher = MetadataMatcherFactory.GetMetadataMatcher(_matchBehavior);
            matcher.SetPictureLists(localPictureSyncMetadataList, flickrPictureSyncMetadataList);

            matchedPicturePairs = matcher.GetMatchedPictures();
            unmatchedFlickrPictures = matcher.GetUnmatchedFlickrPictures();

            string progressMessage = String.Format(Properties.Messages.NumberOfMatchedPictures, matchedPicturePairs.Count);
            _worker.ReportProgress(currentProgress, progressMessage);
        }

        /// <summary>
        /// Fetches the metadata for local and Flickr pictures.
        /// </summary>
        /// <param name="currentProgress">The progress percentage at the start of this step.</param>
        /// <param name="targetProgress">The progress percentage to achieve after this step has completed.</param>
        /// <param name="flickrPictureSyncMetadataList"></param>
        /// <param name="localPictureSyncMetadataList"></param>
        /// <returns></returns>
        private bool FetchPictureMetadata(int currentProgress, int targetProgress, out IList<FlickrPictureMetadata> flickrPictureSyncMetadataList, out IList<LocalPictureMetadata> localPictureSyncMetadataList)
        {
            flickrPictureSyncMetadataList = null;
            localPictureSyncMetadataList = null;

            int progressToAchieve = targetProgress - currentProgress;
            
            _worker.ReportProgress(currentProgress, Properties.Messages.FetchingLocalMetadata);

            IList<string> localPictureFileNames = _localPicturesHelper.GetPictureFileNames(_localFolder, _includeSubFolders);

            currentProgress += (int)(progressToAchieve * 0.1);
            _worker.ReportProgress(currentProgress, Properties.Messages.FetchingFlickrPhotos);

            IList<Photo> flickrPhotoList = _flickrHelper.GetPhotos(_flickrPhotoset);

            currentProgress += (int)(progressToAchieve * 0.1);

            _worker.ReportProgress(currentProgress, Properties.Messages.FetchingFlickrPhotos);

            if (ShouldCancel())
            {
                return true;
            }

            int intermediateTargetProgress = currentProgress + (int)(progressToAchieve * 0.8);
            flickrPictureSyncMetadataList = GetFlickrPictureSyncMetadata(flickrPhotoList, currentProgress, intermediateTargetProgress);

            if (ShouldCancel())
            {
                return true;
            }

            currentProgress = intermediateTargetProgress;

            localPictureSyncMetadataList = GetLocalPictureSyncMetadata(localPictureFileNames, currentProgress, targetProgress);

            if (ShouldCancel())
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Advances the progress from <paramref name="startProgress"/> to <paramref name="targetProgress"/> in <paramref name="numberOfMilliseconds"/>.
        /// </summary>
        /// <param name="progressMessage">The progress message to report.</param>
        /// <param name="numberOfMilliseconds">The time to take.</param>
        /// <param name="startProgress">The progress percentage with which to start.</param>
        /// <param name="targetProgress">The progress percentage with which to end.</param>
        /// <returns>Whether or not to cancel.</returns>
        private bool SimulateProgress(string progressMessage, int numberOfMilliseconds, int startProgress, int targetProgress)
        {
            if (targetProgress <= startProgress)
            {
                return false;
            }

            int sleepTimePerStep = numberOfMilliseconds / (targetProgress - startProgress);

            for (int currentProgress = startProgress; currentProgress <= targetProgress; currentProgress++)
            {
                System.Threading.Thread.Sleep(sleepTimePerStep);

                if (ShouldCancel())
                {
                    return true;
                }

                _worker.ReportProgress(currentProgress, progressMessage);
            }
            return false;
        }

        /// <summary>
        /// Returns the progress percentage.
        /// </summary>
        /// <param name="startProgress">The start percentage.</param>
        /// <param name="targetProgress">The target percentage.</param>
        /// <param name="currentPictureNumber">The number of the picture currently being processed.</param>
        /// <param name="totalNumberOfPictures">The total number of pictures to process.</param>
        /// <returns></returns>
        private static int GetProgressPercentage(int startProgress, int targetProgress, int currentPictureNumber, int totalNumberOfPictures)
        {
            return startProgress + Convert.ToInt32((targetProgress - startProgress) * (currentPictureNumber / Convert.ToDouble(totalNumberOfPictures)));
        }

        /// <summary>
        /// Creates a delegate that reports back progress checks for cancellation.
        /// </summary>
        /// <param name="progressMessageBase">The base for the progress message to which the current progress will be appended.</param>
        /// <param name="startProgress">The progress percentage with which to start.</param>
        /// <param name="targetProgress">The progress percentage with which to end.</param>
        private EventHandler<PictureProgressEventArgs> CreatePictureProgressEventHandler(string progressMessageBase, int startProgress, int targetProgress)
        {
            return delegate(object sender, PictureProgressEventArgs e)
               {
                   if (_worker.CancellationPending)
                   {
                       e.CancelProcess = true;
                       return;
                   }

                   int currentProgress = GetProgressPercentage(startProgress, targetProgress, e.CurrentPictureNumber, e.TotalNumberOfPictures);
                   string progressMessage = progressMessageBase + GetProgressMessage(e.CurrentPictureNumber, e.TotalNumberOfPictures);

                   _worker.ReportProgress(currentProgress, progressMessage);
               };
        }

        /// <summary>
        /// Creates a delegate that can be called to check for cancellation.
        /// </summary>
        /// <returns></returns>
        private ShouldCancelDelegate CreateShouldCancelDelegate()
        {
            return delegate()
            {
                if (_worker.CancellationPending)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            };
        }

        /// <summary>
        /// Download pictures from Flickr and return the metadata to set on the downloaded local pictures.
        /// </summary>
        /// <param name="unmatchedFlickrPictures">The Flickr pictures that do not exist locally.</param>
        /// <param name="startProgress">The progress percentage with which to start.</param>
        /// <param name="targetProgress">The progress percentage with which to finish.</param>
        /// <returns>A list of local picture metadata to update for the downloaded files.</returns>
        private IList<LocalPictureMetadata> DownloadUnmatchedPictures(IList<FlickrPictureMetadata> unmatchedFlickrPictures, int startProgress, int targetProgress)
        {
            IList<string> filePaths = _flickrHelper.DownloadPictures(unmatchedFlickrPictures, _localFolder,
               CreatePictureProgressEventHandler(Properties.Messages.DownloadingFlickrPictures, startProgress, targetProgress),
               CreateShouldCancelDelegate());

            if (filePaths == null)
            {
                return null;
            }

            List<LocalPictureMetadata> downloadedFilesToUpdate = new List<LocalPictureMetadata>(filePaths.Count);

            for (int counter = 0; counter < filePaths.Count; counter++)
            {
                string filePath = filePaths[counter];
                FlickrPictureMetadata flickrPictureMetadata = unmatchedFlickrPictures[counter];

                LocalPictureMetadata localPictureMetadata = GetDownloadedLocalPictureMetadata(filePath, flickrPictureMetadata);

                downloadedFilesToUpdate.Add(localPictureMetadata);
            }

            return downloadedFilesToUpdate.AsReadOnly();
        }

        /// <summary>
        /// Returns a <see cref="LocalPictureMetadata"/> instance for a picture that was downloaded.
        /// </summary>
        /// <param name="filePath">The file name and path of the local file.</param>
        /// <param name="flickrPictureMetadata">The metadata of the corresponding Flickr picture.</param>
        /// <returns>A <see cref="LocalPictureMetadata"/> instance for a picture that was downloaded.</returns>
        private LocalPictureMetadata GetDownloadedLocalPictureMetadata(string filePath, FlickrPictureMetadata flickrPictureMetadata)
        {
            // Read the metadata in file for the just downloaded picture, because it might contain old GPS coordinates and such that need overwriting.
            LocalPictureMetadata localPictureMetadata = _localPicturesHelper.GetSyncMetadata(filePath);

            localPictureMetadata.TrackChanges = true;

            MetadataSyncher.CopyMetadata(flickrPictureMetadata, localPictureMetadata);

            return localPictureMetadata;
        }

        /// <summary>
        /// Updates the metadata in Flickr pictures.
        /// </summary>
        /// <param name="updatedFlickrPictures">The Flickr pictures that need updating.</param>
        /// <param name="startProgress">The progress percentage with which to start.</param>
        /// <param name="targetProgress">The progress percentage with which to finish.</param>
        private void UpdateFlickrPictureMetadata(IList<FlickrPictureMetadata> updatedFlickrPictures, int startProgress, int targetProgress)
        {
            _flickrHelper.UpdatePictureMetadata(updatedFlickrPictures,
               CreatePictureProgressEventHandler(Properties.Messages.UpdatingFlickrPictures, startProgress, targetProgress));
        }

        /// <summary>
        /// Updates the metadata in local pictures.
        /// </summary>
        /// <param name="updatedLocalPictures">The local pictures that need updating.</param>
        /// <param name="startProgress">The progress percentage with which to start.</param>
        /// <param name="targetProgress">The progress percentage with which to finish.</param>
        private void UpdateLocalPictureMetadata(IList<LocalPictureMetadata> updatedLocalPictures, int startProgress, int targetProgress)
        {
            _localPicturesHelper.FlickrIdBehavior = Properties.Settings.Default.FlickrIdBehavior;

            _localPicturesHelper.UpdatePictureMetadata(updatedLocalPictures,
               CreatePictureProgressEventHandler(Properties.Messages.UpdatingLocalPictures, startProgress, targetProgress));
        }

        /// <summary>
        /// Returns a list of Flickr pictures for which the metadata should be updated.
        /// </summary>
        /// <param name="synchedPicturePairs">The list of synched picture pairs.</param>
        /// <returns>A list of Flickr pictures for which the metadata should be updated.</returns>
        private static List<FlickrPictureMetadata> GetUpdatedFlickrPictures(IList<PicturePair> synchedPicturePairs)
        {
            List<FlickrPictureMetadata> updatedFlickrPictures = new List<FlickrPictureMetadata>(synchedPicturePairs.Count);

            foreach (PicturePair picturePair in synchedPicturePairs)
            {
                if (picturePair.FlickrMetadata.Changed)
                {
                    updatedFlickrPictures.Add(picturePair.FlickrMetadata);
                }
            }

            return updatedFlickrPictures;
        }

        /// <summary>
        /// Returns a list of local pictures for which the metadata should be updated.
        /// </summary>
        /// <param name="synchedPicturePairs">The list of synched picture pairs.</param>
        /// <returns>A list of local pictures for which the metadata should be updated.</returns>
        private static List<LocalPictureMetadata> GetUpdatedLocalPictures(IList<PicturePair> synchedPicturePairs)
        {
            List<LocalPictureMetadata> updatedLocalPictures = new List<LocalPictureMetadata>(synchedPicturePairs.Count);

            foreach (PicturePair picturePair in synchedPicturePairs)
            {
                if (picturePair.LocalMetadata.Changed)
                {
                    updatedLocalPictures.Add(picturePair.LocalMetadata);
                }
            }

            return updatedLocalPictures;
        }

        /// <summary>
        /// Gets the metadata to sync for the local set.
        /// </summary>
        /// <param name="localPictureFileNames">The local set.</param>
        /// <param name="startProgress">The progress percentage with which to start.</param>
        /// <param name="targetProgress">The progress percentage with which to finish.</param>
        /// <returns>The list with metadata. Null if cancelled.</returns>
        private IList<LocalPictureMetadata> GetLocalPictureSyncMetadata(IList<string> localPictureFileNames, int startProgress, int targetProgress)
        {
            IList<LocalPictureMetadata> localPictureSyncMetadataList = _localPicturesHelper.GetSyncMetadata(localPictureFileNames,
               CreatePictureProgressEventHandler(Properties.Messages.ReadingLocalPictures, startProgress, targetProgress));

            return localPictureSyncMetadataList;
        }


        /// <summary>
        /// Gets the metadata to sync for the Flickr set.
        /// </summary>
        /// <param name="flickrPhotoList">The Flickr set.</param>
        /// <param name="startProgress">The progress percentage with which to start.</param>
        /// <param name="targetProgress">The progress percentage with which to finish.</param>
        /// <returns>The list with metadata. Null if cancelled.</returns>
        private IList<FlickrPictureMetadata> GetFlickrPictureSyncMetadata(IList<Photo> flickrPhotoList, int startProgress, int targetProgress)
        {
            IList<PhotoInfo> flickrPhotoInfoList = _flickrHelper.GetPhotoInfo(flickrPhotoList,
               CreatePictureProgressEventHandler(Properties.Messages.ReadingFlickrPictures, startProgress, targetProgress));

            if (flickrPhotoInfoList == null)
            {
                return null;
            }
            else
            {
                return FlickrMetadataHelper.ExtractSyncMetadata(flickrPhotoInfoList);
            }
        }


        /// <summary>
        /// Returns a progress message, like Progress: 3 of 6.
        /// </summary>
        /// <param name="currentPictureNumber">The current picture number.</param>
        /// <param name="totalNumberOfPictures">The total number of pictures.</param>
        /// <returns>The progress message.</returns>
        private static string GetProgressMessage(int currentPictureNumber, int totalNumberOfPictures)
        {
            return String.Format(CultureInfo.CurrentCulture, Properties.Messages.PictureProgress, currentPictureNumber, totalNumberOfPictures);
        }

        /// <summary>
        /// Checks if the sync process should be cancelled.
        /// </summary>
        /// <returns><c>True</c> if cancelled, <c>false</c> otherwise.</returns>
        private bool ShouldCancel()
        {
            if (_worker.CancellationPending)
            {
                _worker.ReportProgress(0, Properties.Messages.SyncCancelled);
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Notifies subscribed parties that a propery value has changed.
        /// </summary>
        /// <param name="propertyName">The name of the property for which the value has changed.</param>
        private void NotifyPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Initializes this instance.
        /// </summary>
        /// <param name="flickrHelper">The Flickr helper object to use.</param>
        public void Initialize(FlickrHelper flickrHelper)
        {
            if (flickrHelper == null)
            {
                throw new ArgumentNullException("flickrHelper");
            }
            _flickrHelper = flickrHelper;

            _flickrHelper.PropertyChanged += FlickrHelper_PropertyChanged;

            _localPicturesHelper = new LocalPicturesHelper();

            InitializeLocalFolder();

            IncludeSubFolders = Properties.Settings.Default.IncludeSubFolders;
            SimulateUpdatingMetadata = Properties.Settings.Default.SimulateUpdatingPictures;
            TagsSyncBehavior = Properties.Settings.Default.TagsSyncBehavior;
            MatchBehavior = Properties.Settings.Default.MatchBehavior;

            DownloadMissingPictures = Properties.Settings.Default.DownloadMissingPictures;

            _isInitialized = true;
        }

        /// <summary>
        /// Event handler for when properties change on the <see cref="FlickrHelper"/> instance
        /// contained by this instance.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event arguments.</param>
        private void FlickrHelper_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsFlickrProUser")
            {
                NotifyPropertyChanged("CanDownloadPictures");
                NotifyPropertyChanged("DownloadMissingPictures");
            }
        }

        /// <summary>
        /// Do the synchronization with the selected parameters.
        /// </summary>
        /// <param name="sender">The <see cref="BackgroundWorker"/> controlling the background
        /// process.</param>
        /// <param name="e">Information on the background process.</param>
        public void DoSync(object sender, DoWorkEventArgs e)
        {
            BusySynching = true;

            try
            {
                _worker = (BackgroundWorker)sender;

                EnsureIsInitialized();
                EnsureRequiredPropertiesAreSet();

                DoSync();
            }
            catch (FlickrHelperException ex)
            {
                Trace.TraceError(ex.ToString());

                string message = String.Format(CultureInfo.CurrentCulture, Messages.SyncFailed, ex.Message);

                _worker.ReportProgress(0, message);
            }
            finally
            {
                BusySynching = false;
            }
        }

        #endregion

        #region INotifyPropertyChanged Members
        /// <summary>
        /// Event to notify of changes in property values.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
    }
}
